package com.um.jdy.app.charge.manager;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.common.models.AlipayTradeCreateResponse;
import com.baomidou.lock.annotation.Lock4j;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.um.jdy.app.charge.entity.dto.PBStartParam;
import com.um.jdy.app.charge.entity.vo.OrderItemVO;
import com.um.jdy.app.charge.service.impl.WxPayScoreServiceImpl;
import com.um.jdy.baofu.api.BaofuPayService;
import com.um.jdy.common.baofu.entity.po.BaofuRefund;
import com.um.jdy.common.baofu.service.BaofuRefundService;
import com.um.jdy.common.commons.manager.WxManager;
import com.um.jdy.app.device.manager.DeviceManager;
import com.um.jdy.common.base.enums.YesOrNo;
import com.um.jdy.common.base.service.SettingService;
import com.um.jdy.common.charge.code.OrderCode;
import com.um.jdy.common.charge.entity.po.DataLog;
import com.um.jdy.common.charge.entity.po.DepositOrder;
import com.um.jdy.common.charge.entity.po.Order;
import com.um.jdy.common.charge.entity.po.PbOrder;
import com.um.jdy.common.charge.entity.vo.OrderVO;
import com.um.jdy.common.charge.enums.OrderAction;
import com.um.jdy.common.charge.enums.OrderEnum;
import com.um.jdy.common.charge.enums.OrderException;
import com.um.jdy.common.charge.enums.OrderStopReason;
import com.um.jdy.common.charge.service.*;
import com.um.jdy.common.commons.service.CommissionService;
import com.um.jdy.common.device.code.DeviceCode;
import com.um.jdy.common.device.entity.po.FeePbPrice;
import com.um.jdy.common.device.entity.po.Gun;
import com.um.jdy.common.device.entity.po.Pile;
import com.um.jdy.common.device.entity.po.Station;
import com.um.jdy.common.device.enums.DeviceEnum;
import com.um.jdy.common.device.service.FeePbPriceService;
import com.um.jdy.common.device.service.GunService;
import com.um.jdy.common.device.service.PileService;
import com.um.jdy.common.device.service.StationService;
import com.um.jdy.common.finance.enums.FinancialEnum;
import com.um.jdy.common.finance.service.PlatformBillService;
import com.um.jdy.common.finance.service.UserBillService;
import com.um.jdy.common.member.entity.po.MemberUser;
import com.um.jdy.common.member.enums.PayEnum;
import com.um.jdy.common.member.service.MemberUserService;
import com.um.jdy.common.platform.entity.po.App;
import com.um.jdy.common.platform.entity.po.RentSetting;
import com.um.jdy.common.platform.service.AppService;
import com.um.jdy.common.platform.service.RentSettingService;
import com.um.jdy.common.push.service.PushService;
import com.um.jdy.rpc.entity.PBPort;
import com.um.jdy.rpc.enums.DeviceProtocolEnum;
import com.um.jdy.rpc.enums.DeviceTypeEnum;
import com.um.jdy.rpc.factory.ServiceFactory;
import com.um.jdy.rpc.notify.RpcPBOrderNotifyService;
import com.um.springboot.starter.code.CommonCode;
import com.um.springboot.starter.exception.ServiceException;
import com.um.springboot.starter.orm.manager.BaseManager;
import com.um.springboot.starter.properties.HshProperties;
import com.um.springboot.starter.utils.OrderUtil;
import com.um.springboot.starter.utils.PayUtil;
import com.um.springboot.starter.utils.StringUtil;
import com.um.springcloud.dubbo.cluster.DeviceKey;
import com.um.weixin.pay.service.WxBasePayService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * @Title
 * @Description
 * @Author renjunwei
 * @Date 2023/5/22 10:37
 */
@Slf4j
@Service
@DubboService
public class PBOrderManager extends BaseManager<PbOrderService> implements RpcPBOrderNotifyService {

    @Autowired
    MemberUserService userService;

    @Autowired
    PileService pileService;

    @Autowired
    GunService gunService;

    @Autowired
    SettingService settingService;

    @Autowired
    OrderService orderService;

    @Autowired
    PbOrderService pbOrderService;

    @Autowired
    DeviceManager deviceManager;

    @Autowired
    FeePbPriceService feePbPriceService;

    @Autowired
    DepositOrderService depositOrderService;

    @Autowired
    ServiceFactory serviceFactory;

    @Autowired
    DataLogService dataLogService;

    @Autowired
    HshProperties hshProperties;

    @Autowired
    WxManager wxManager;

    @Autowired
    WxBasePayService wxBasePayService;

    @Autowired
    WxPayScoreServiceImpl wxPayScoreService;

    @Autowired
    UserBillService userBillService;

    @Autowired
    PlatformBillService platformBillService;

    @Autowired
    CommissionService commissionService;

    @Autowired
    OrderSharingService orderSharingService;

    @Autowired
    StationService stationService;

    @Autowired
    PBOrderManager pbOrderManager;

    @Autowired
    TaskScheduler taskScheduler;

    @Autowired
    RentSettingService rentSettingService;

    @Autowired
    PushService pushService;
    @Autowired
    BaofuPayService baofuPayService;

    @Autowired
    AppService appService;

    @Autowired
    private BaofuRefundService baofuRefundService;

    /**
     * 开始租借充电宝
     * {@link #createDepositOrder} 第1步：创建押金订单
     * {@link #depositOrderPaid} 第2步：押金支付成功
     * {@link #createChargeOrder} 第3步：创建充电宝租借订单
     * {@link #updateOrderStatus} 第4步：更新充电宝租借订单状态
     * {@link #rentFinish} 第5步：租借订单结束
     * {@link #payFinish} 第6步：充电金额支付完成
     * @param params
     * @return
     */
    @Transactional
    public Map startRent(PBStartParam params) {
        MemberUser user = userService.get();

        //获取可用设备
        Pile pile = getAvailableDevice(params.getCode());

        //判断租借订单是否达到上限
        checkRentLimit(pile.getComId(), user.getMemberUserId());

        //获取用户免费充电状态
        boolean isFree = getFreeStatus(user.getMemberUserId(), pile.getDeviceStationId(), pile.getDevicePileId(),pile.getComId());

        //免费充电直接创建订单
        if(isFree){
            String orderNo = OrderUtil.getOrderNo();
            createChargeOrder(orderNo, OrderUtil.getTxOrderNo(), params.getCode(), user.getMemberUserId(),
                    OrderEnum.CreditType.Other.name());
            return MapUtil.of("order_no",orderNo);
        }

        //创建押金订单
        return createDepositOrder(pile, user, params.getCreditType(), params.getDepositPayWay(), params.getDepositAutoReturn());
    }

    /**
     * 第1步：创建押金订单
     * @param pile
     * @param user
     * @param payWay
     * @param creditType
     * @param autoReturn
     * @return
     */
    public Map createDepositOrder(Pile pile, MemberUser user, OrderEnum.CreditType creditType, PayEnum.PayWay payWay, Boolean autoReturn){

        FeePbPrice fee = feePbPriceService.getRow("device_fee_id", pile.getDeviceFeeId());
        if(null == fee){
            throw new ServiceException(DeviceCode.NoFee);
        }

        DepositOrder depositOrder = new DepositOrder();
        depositOrder.setComId(pile.getComId());
        depositOrder.setOrderNo(OrderUtil.getOrderNo());
        depositOrder.setOutTradeNo(OrderUtil.getWxOrderNo());
        depositOrder.setMemberUserId(user.getMemberUserId());
        depositOrder.setCode(pile.getCode());
        depositOrder.setAmount(fee.getDeposit());
        depositOrder.setReturnAmount(fee.getDeposit());
        depositOrder.setCreateTime(DateUtil.date());
        depositOrder.setPayStatus(PayEnum.Status.UnPayed.name());
        depositOrder.setCreditType(creditType.name());

        if(OrderEnum.CreditType.Deposit == creditType){
            depositOrder.setPayWay(payWay.name());
            depositOrder.setAutoReturn(autoReturn ? YesOrNo.Yes.name() : YesOrNo.No.name());
        }

        depositOrderService.saveRow(depositOrder);

        HashMap returnData = MapUtil.of("order_no", depositOrder.getOrderNo());

        //在线支付押金，根据payWay付款方式创建支付订单
        if(OrderEnum.CreditType.Deposit == creditType){

            String notifyUrl = StrUtil.builder(hshProperties.getConfig().getDomain(),
                    "/api/v1/order/charge/pb/deposit/%s/notify/", userService.getComId()).toString();

            switch (payWay){
                case Wxpay:
                    /** todo 修改为宝付支付
                    WxPayUnifiedOrderV3Result.JsapiResult jsapiResult;
                    try {
                        wxManager.getPayService(userService.getComId());
                        jsapiResult = wxBasePayService.createOrderByJsapi(depositOrder.getOutTradeNo(), user.getMiniOpenId(),
                                PayUtil.toTotalFee(fee.getDeposit()), PayEnum.Title.Deposit.getValue(), String.format(notifyUrl, "wxpay"));
                    } catch (WxPayException e) {
                        log.error("微信支付创建订单失败", e);
                        throw new ServiceException(e.getCustomErrorMsg());
                    }

                    returnData.putAll(BeanUtil.beanToMap(jsapiResult, true, true));
                     */
                    try{
                        String type = "chargePb";
                        App app = appService.getEntity("com_id", userService.getComId());
                        Map<String, Object> returnData1 = baofuPayService.createOrderByJsapi(depositOrder.getOutTradeNo(),PayUtil.toTotalFee(fee.getDeposit()),PayEnum.Title.Charge.getValue(),user.getMiniOpenId(),app.getMiniAppId(),app.getWxMchId(),type,userService.getComId());
                        //Map<String, Object> returnData = null;
                        returnData.putAll(returnData1);
                    }catch (Exception e){
                        log.error("宝付支付创建订单失败", e);
                        throw new ServiceException(e.getMessage());
                    }
                    break;

                case Alipay:
                    AlipayTradeCreateResponse alipayTradeCreateResponse = null;
                    try {
                        alipayTradeCreateResponse = Factory.Payment.Common()
                                .asyncNotify(String.format(notifyUrl, "alipay"))
                                .optional("timeout_express", "10m") //订单超时时间
                                .create(PayEnum.Title.Deposit.getValue(), depositOrder.getOutTradeNo(),
                                        PayUtil.toDoubleAmountStr(fee.getDeposit()), user.getAlipayUserId());
                    } catch (Exception e) {
                        log.error("支付宝创建订单失败", e);
                        throw new ServiceException(OrderCode.CreateAlipayOrderFailure);
                    }

                    if (!ResponseChecker.success(alipayTradeCreateResponse)) {
                        throw new ServiceException(CommonCode.Failed.getCode(), alipayTradeCreateResponse.getSubMsg());
                    }

                    returnData.putAll(alipayTradeCreateResponse.toMap());
                    break;
            }
        }

        //使用微信支付分
        else if (OrderEnum.CreditType.WxPayScore == creditType) {
            wxPayScoreService.create(depositOrder.getOutTradeNo());
        }

        return returnData;
    }

    /**
     * 第2步：押金支付成功
     *
     * @param orderNo
     * @param payTime
     * @throws Exception
     */
    @Lock4j(keys = "#orderNo")
    @Transactional
    public void depositOrderPaid(String orderNo, Date payTime) {
        DepositOrder depositOrder = depositOrderService.getRow("order_no", orderNo);
        if (depositOrder == null) {
            log.error("押金订单{}不存在！", orderNo);
            return;
        }

        if (PayEnum.Status.Payed.name().equals(depositOrder.getPayStatus())) {
            log.warn("订单{}已支付，操作结束", orderNo);
            return;
        }

        DepositOrder update = new DepositOrder();
        update.setChargeDepositOrderId(depositOrder.getChargeDepositOrderId());
        update.setPayTime(payTime);
        update.setPayStatus(PayEnum.Status.Payed.name());
        depositOrderService.saveRow(update);


        //插入用户账单流水：押金充值
        userBillService.addBill(depositOrder.getComId(), depositOrder.getMemberUserId(), depositOrder.getOutTradeNo(),
                depositOrder.getOrderNo(), depositOrder.getAmount(), FinancialEnum.IncomeOrExpend.Expend,
                FinancialEnum.UserBillEvent.DepositRecharge, FinancialEnum.BillStatus.Effective);

        //插入平台账单流水：押金充值
        platformBillService.addBill(depositOrder.getComId(), depositOrder.getOutTradeNo(), depositOrder.getOrderNo(),
                depositOrder.getAmount(), FinancialEnum.IncomeOrExpend.Income,
                FinancialEnum.PlatformBillEvent.DepositRecharge, FinancialEnum.BillStatus.Effective);

        this.createChargeOrder(depositOrder.getOrderNo(), depositOrder.getOutTradeNo(), depositOrder.getCode(),
                depositOrder.getMemberUserId(), depositOrder.getCreditType());
    }

    /**
     * 第3步：创建充电宝租借订单
     * @orderNo 租借订单号与押金订单号一致
     * @return
     */
    public void createChargeOrder(String orderNo,String outTradeNo,String code,String userId,String creditType){
        Pile pile = getAvailableDevice(code);
        checkRentLimit(pile.getComId(), userId);

        FeePbPrice fee = feePbPriceService.getRow("device_fee_id", pile.getDeviceFeeId());
        if(null == fee){
            throw new ServiceException(DeviceCode.NoFee);
        }

        Order order = new Order();
        order.setOrderNo(orderNo);
        order.setOutTradeNo(outTradeNo);
        order.setDeviceType(pile.getDeviceType());
        order.setDeviceStationId(pile.getDeviceStationId());
        order.setDevicePileId(pile.getDevicePileId());
        order.setDeviceFeeId(pile.getDeviceFeeId());
        order.setPlatformMerchantId(pile.getPlatformMerchantId());
        order.setDeviceName(pile.getName());
        order.setDeviceImei(pile.getImei());
        order.setDeviceCode(pile.getCode());
        order.setMemberUserId(userId);
        order.setComId(pile.getComId());
        order.setCreateTime(DateUtil.date());
        order.setCreditType(creditType);
        switch (OrderEnum.CreditType.valueOf(creditType)){
            case Deposit:
                order.setPayWay(PayEnum.PayWay.Deposit.name());
                break;
            case WxPayScore:
                //order.setPayWay(PayEnum.PayWay.Wxpay.name());
                break;
        }
        order.setStatus(OrderEnum.Status.NotStarted.name());
        order.setPayStatus(PayEnum.Status.UnPayed.name());
        order.setItemPrice(getItemPriceText(fee));
        order.setIsFree(getFreeStatus(userId, pile.getDeviceStationId(), pile.getDevicePileId(),pile.getComId()) ?
                YesOrNo.Yes.name() : YesOrNo.No.name());
        orderService.saveRow(order);

        PbOrder pbOrder = new PbOrder();
        pbOrder.setComId(order.getComId());
        pbOrder.setChargeOrderId(order.getChargeOrderId());
        pbOrder.setDeposit(fee.getDeposit());
        pbOrder.setPrice(fee.getPrice());
        pbOrder.setDaysAmount(fee.getDaysAmount());
        pbOrder.setDuration(fee.getDuration());
        pbOrder.setFreeDuration(fee.getFreeDuration());
        pbOrder.setMaxAmount(fee.getMaxAmount());
        pbOrderService.saveRow(pbOrder);

        MemberUser user = userService.getEntity(userId);
        Station station = stationService.getEntity(pile.getDeviceStationId());
        //用户如果还没有填写省市区，则自动保存
        if(user != null && station != null && StringUtil.isEmpty(user.getProvince())){
            userService.saveLocation(user,station.getProvince(),station.getCity(),station.getArea());
        }

        //记录分佣副本
        orderSharingService.addOrderSharing(order);

        //更新押金订单的充电订单id
        DepositOrder depositOrder = depositOrderService.getRow("order_no", orderNo);
        if(null != depositOrder){
            depositOrder.setChargeOrderId(order.getChargeOrderId());
            depositOrderService.saveRow(depositOrder);
        }

        Map data = MapUtil.builder().put("seq", order.getChargeOrderId()).put("imei", pile.getImei()).put("eqp",80).put("overtime",5).build();
        //下发租借指令给设备
        try {
            //查询可以租借的充电宝
            List<Gun> gunList = gunService.getRows("device_pile_id", pile.getDevicePileId(),"soc","desc");
            Gun rentGun = null;
            for(Gun gun : gunList){
                if(!StringUtil.isEmpty(gun.getSn()) && gun.getSoc() >= 80) {
                    rentGun = gun;
                    break;
                }
            }

            //不为空才能租借成功
            if(rentGun != null) {
                String command = serviceFactory.getRpcPBDeviceService(pile.getProtocol())
                        .reqRent(order.getChargeOrderId(), new DeviceKey(pile.getImei()), rentGun.getPort(),80, 5);

                saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.StartRentReq, command, "无");
            }

            // 创建30秒后出柜失败，则自动结束订单
            taskScheduler.schedule(() -> cancelOrder(orderNo, OrderStopReason.PB.OVERTIME_UNSTART),
                    DateUtil.date().offset(DateField.SECOND, 30));

        }catch (Exception ex){
            log.error("下发租借指令失败", ex);
            saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.StartRentReqFailed,data, "无");
        }
    }

    /**
     * 第4步：根据设备上报更新充电宝租借订单状态
     * @param orderId
     * @param state 租借结果 0-为正常，1-为没有充电宝，2-为无可租用，3-超时
     * @param sn 充电宝SN
     * @param sn 借出端口
     */
    @Transactional
    public void updateOrderStatus(String orderId,Integer state,String sn,Integer port){
        Order order = orderService.getRow(orderId);
        if(order == null){
            log.info("设备上报租借订单不存在，订单ID：{}，sn:{}", orderId, sn);
            return;
        }

        PbOrder pbOrder = pbOrderService.getRow("charge_order_id", orderId);

        if(state == 0){
            pbOrder.setSn(sn);
            pbOrderService.updateRow(pbOrder);

            saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.DeviceOpenSuccess, new HashMap(), "无");

            Gun gun = gunService.getRow(new String[]{"device_pile_id","port"},new Object[]{order.getDevicePileId(),port});
            if (null == gun) {
                log.warn("租借设备端口号：{}不存在，忽略更新端口状态", port);
                saveChargeData(order.getComId(), orderId, OrderAction.PB.DeviceOpenSuccess,
                        new HashMap(), String.format("租借设备端口号：%s不存在，忽略更新端口状态", port));
            }else{

                gun.setSn(StrUtil.EMPTY);
                gun.setSoc(0);
                gunService.saveRow(gun);

                order.setDeviceGunId(gun.getDeviceGunId());
                order.setPort(port);
                orderService.updateRow(order);

                Pile pile = pileService.getRow(gun.getDevicePileId());
                if(pile == null)
                    return;

                //欣华设备才需要再次查询端口确认是否出柜成功
                if(StringUtil.equals(pile.getProtocol(), DeviceProtocolEnum.XH_TCP_10.name())) {
                    queryPortStatus(order.getComId(), order.getDevicePileId(), order.getChargeOrderId(), order.getPort());
                }else{
                    //沃诗的直接确认：出柜成功，sn为null
                    confirmOrderStatus(orderId,null);
                }
                //再次下发查询指定端口来确认是否出柜成功
//                taskScheduler.schedule(() -> ,
//                        DateUtil.date().offset(DateField.SECOND, 5));

            }
        }else {

            // TODO state做个枚举
            String reason = "没有充电宝";
            if(state == 2)
                reason = "无可租用";
            else if(state == 3)
                reason = "设备超时";

            saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.DeviceOpenFail, new HashMap(),
                    String.format("出柜失败原因：%s", reason));

            order.setStatus(OrderEnum.Status.Failure.name());
            order.setStartTime(DateUtil.date());
            order.setStartTime(order.getStartTime());
            orderService.updateRow(order);

            if(OrderEnum.CreditType.Deposit.name().equals(order.getCreditType())){
                DepositOrder depositOrder = depositOrderService.getRow("order_no", order.getOrderNo());
                if(depositOrder != null){
                    returnDepositOrder(depositOrder);//退还押金
                }
            }
        }



    }

    /**
     * 第5步：租借订单结束
     *
     * @param orderNo 订单号
     * @param devicePileId 归还设备ID
     * @param port 端口实体
     * @param isBuy 端口实体
     * @param isTimeout 端口实体
     */
    @Lock4j(keys = "#orderNo")
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public void rentFinish(String orderNo,String stopReason, String devicePileId, Integer port, boolean isBuy, boolean isTimeout) {
        //更新订单状态
        Order order = orderService.getRow("order_no", orderNo);
        if (order == null) {
            log.warn("订单{}不存在，rentFinish中断", orderNo);
            return;
        }

        if(OrderEnum.Status.Completed.name().equals(order.getStatus())) {
            log.warn("订单{}已结束，rentFinish中断", orderNo);
            saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.OrderHasFinished, new HashMap(),
                    OrderAction.PB.OrderHasFinished.getValue());
            return;
        }

        //归还设备信息
        PbOrder pbOrder = pbOrderService.getRow("charge_order_id", order.getChargeOrderId());
        Pile pile = pileService.getRow(devicePileId);
        if(pile != null) {
            pbOrder.setReturnStationId(pile.getDeviceStationId());
            pbOrder.setReturnDeviceCode(pile.getCode());
            pbOrder.setReturnPort(port);
        }

        order.setEndTime(DateUtil.date());

        int rentDuration = getOrderRentSeconds(order);
        int paySeconds = pbOrderService.getCeilTime(rentDuration,pbOrder.getFreeDuration(), pbOrder.getDuration());
        int chargeAmount = pbOrderService.getChargeAmount(pbOrder, paySeconds);

        order.setDuration(rentDuration);
        order.setPaySeconds(paySeconds);//计算收费时间
        order.setChargeAmount(chargeAmount);//租用金额
        order.setOrderAmount(chargeAmount);
        order.setPayAmount(chargeAmount);//实际支付金额
        order.setStatus(OrderEnum.Status.Completed.name());
        order.setEndType(stopReason);

        if(isBuy || isTimeout){
            order.setPayAmount(pbOrder.getMaxAmount());
            order.setOrderAmount(pbOrder.getMaxAmount());
            order.setChargeAmount(pbOrder.getMaxAmount());

            pbOrder.setIsBuy(isBuy ? YesOrNo.Yes.name() : YesOrNo.No.name());
            pbOrder.setIsTimeout(isTimeout ? YesOrNo.Yes.name() : YesOrNo.No.name());
        }

        orderService.updateRow(order);
        pbOrderService.updateRow(pbOrder);

        //开始扣费

        //免费订单
        if(YesOrNo.Yes.name().equals(order.getIsFree())){

            order.setPayWay(PayEnum.PayWay.None.name());
            order.setPayTime(DateUtil.date());
            order.setPayStatus(PayEnum.Status.Payed.name());
            order.setFreeAmount(order.getChargeAmount());
            order.setOrderAmount(0);
            order.setChargeAmount(0);
            order.setPayAmount(0);
            orderService.updateRow(order);

            payFinish(orderNo);
        }

        //微信支付分自动扣费
        else if(OrderEnum.CreditType.WxPayScore.name().equals(order.getCreditType())){
            order.setPayWay(PayEnum.PayWay.Wxpay.name());
            orderService.updateRow(order);

            saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.WxscorePay,new HashMap(), "微信支付分开始支付");
            wxPayScoreService.complete(order.getOutTradeNo(), order.getChargeAmount());
            payFinish(orderNo);
            saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.WxscorePay,new HashMap(), "微信支付分结束支付");
        }

        //在线支付押金订单直接使用押金扣充电金额
        else if(OrderEnum.CreditType.Deposit.name().equals(order.getCreditType())){
            order.setPayWay(PayEnum.PayWay.Deposit.name());
            orderService.updateRow(order);

            DepositOrder depositOrder = depositOrderService.getRow("charge_order_id", order.getChargeOrderId());
            depositOrder.setPayOrderAmount(order.getPayAmount());
            depositOrder.setReturnAmount(depositOrder.getAmount() - order.getPayAmount());
            depositOrderService.updateRow(depositOrder);

            saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.DepositWithholding,
                    new HashMap(), "无");

            payFinish(orderNo);

            //自动退回押金
            if(depositOrder.getReturnAmount() > 0 && YesOrNo.Yes.name().equals(depositOrder.getAutoReturn())){
                returnDepositOrder(depositOrder);
            }

        }

        //推送
        pushService.pushChargeEnd(order);

    }

    /**
     * 取消订单
     * @param orderNo
     * @param endType
     * @throws Exception
     */
    @Lock4j(keys = "#orderNo")
    @Transactional
    public void cancelOrder(String orderNo, OrderStopReason.PB endType) {
        // 重新查一遍订单，避免落锁前订单已被改动
        Order order = orderService.getRow("order_no", orderNo);
        if (order == null) {
            throw new ServiceException("操作失败，订单不存在！");
        }

        if (!OrderEnum.Status.NotStarted.name().equals(order.getStatus())) {
            throw new ServiceException("操作失败，当前状态不允许取消订单！");
        }

        Order updateOrder = new Order();
        updateOrder.setChargeOrderId(order.getChargeOrderId());
        updateOrder.setEndType(endType.name());
        updateOrder.setStatus(OrderEnum.Status.Failure.name());
        updateOrder.setExceptionType(OrderException.PB.PopupFailed.name());
        updateOrder.setIsException(YesOrNo.Yes.name());
        updateOrder.setExceptionStatus(YesOrNo.No.name());
        orderService.updateRow(updateOrder);


        saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.DeviceTimeout,
                new HashMap(), endType.getValue());

        //自动退回押金
        if(OrderEnum.CreditType.Deposit.name().equals(order.getCreditType())){
            DepositOrder depositOrder = depositOrderService.getRow("order_no", order.getOrderNo());
            if(depositOrder != null){
                returnDepositOrder(depositOrder);
            }
        }
    }

    /**
     * 再次确认充电宝是否出柜成功
     * @param orderId
     */
    @Transactional
    public void confirmOrderStatus(String orderId, String sn){
        Order order = orderService.getRow(orderId);
        if(order == null){
            log.info("设备上报租借充电宝状态订单不存在，订单ID：{}", orderId);
            return;
        }

        //sn不存在，确认厨柜成功
        if(StrUtil.isEmpty(sn)){
            Order updateOrder = new Order();
            updateOrder.setChargeOrderId(orderId);
            updateOrder.setStatus(OrderEnum.Status.Charging.name());
            updateOrder.setStartTime(DateUtil.date());
            updateOrder.setEndTime(DateUtil.date());
            orderService.updateRow(updateOrder);

            log.debug("出柜成功:{}",updateOrder);
            saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.ConfirmOrderSuccess, new Hashtable<>(), "确认订单出柜成功");
            return;
        }

        log.debug("出柜成功1:{}",order);
        order.setStatus(OrderEnum.Status.Failure.name());
        order.setEndType(OrderStopReason.PB.OVERTIME_UNSTART.name());
        order.setEndTime(DateUtil.date());
        orderService.saveRow(order);

        saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.ConfirmOrderFail, MapUtil.builder().put("sn",sn).build(),"确认订单出柜失败");

        //自动退回押金
        if(OrderEnum.CreditType.Deposit.name().equals(order.getCreditType())){
            DepositOrder depositOrder = depositOrderService.getRow("order_no", order.getOrderNo());
            if(depositOrder != null){
                returnDepositOrder(depositOrder);
            }
        }
    }

    /**
     * 第6步：充电金额支付完成
     * @param orderNo
     */
    @Lock4j(keys = "#orderNo")
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public void payFinish(String orderNo){
        Order order = orderService.getRow("order_no", orderNo);
        if (order == null) {
            log.warn("订单{}不存在，payFinish中止", orderNo);
            return;
        }

        if(PayEnum.Status.Payed.name().equals(order.getPayStatus())) {
            log.warn("订单{}已支付，payFinish中止", orderNo);
            return;
        }

        order.setPayTime(DateUtil.date());
        order.setPayStatus(PayEnum.Status.Payed.name());

        //充电宝成本。买断或超时长，充电宝不归还，分佣金额需减掉成本
        Integer payAmount = order.getPayAmount();
        //PbOrder pbOrder = pbOrderService.getRow("charge_order_id", order.getChargeOrderId());

        //不减充电宝成本了
        /*if(YesOrNo.Yes.name().equals(pbOrder.getIsBuy()) || YesOrNo.Yes.name().equals(pbOrder.getIsTimeout())){
            RentSetting rentSetting = rentSettingService.getRow("com_id", order.getComId());
            payAmount -= (null == rentSetting ? 0 : rentSetting.getPowerPrice() * 100);
        }*/

        // 校验是否进行分佣
        if (payAmount > 0 && !YesOrNo.Yes.name().equals(order.getIsFree())) {
            saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.StartCalcCommission, new HashMap(),
                    "无");
            int profit = commissionService.calcCommission(order.getChargeOrderId(), order.getCreateTime(), orderNo,order.getOutTradeNo(), payAmount);
            order.setProfit(profit);

            saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.StopCalcCommission, new HashMap(),
                    "订单利润："+(profit/100.0)+"元");

        }

        orderService.saveRow(order);

        //押金类型不需要记录充电支出流水，押金提现流水已减掉了充电支出
        if(!OrderEnum.CreditType.Deposit.name().equals(order.getCreditType())){

            //插入用户账单流水：充电支出
            userBillService.addBill(order.getComId(), order.getMemberUserId(), order.getOutTradeNo(),
                    order.getOrderNo(), order.getPayAmount(), FinancialEnum.IncomeOrExpend.Expend,
                    FinancialEnum.UserBillEvent.Charge, FinancialEnum.BillStatus.Effective);

            //插入平台账单流水：充电收入
            platformBillService.addBill(order.getComId(), order.getOutTradeNo(), order.getOrderNo(),
                    order.getPayAmount(), FinancialEnum.IncomeOrExpend.Income,
                    FinancialEnum.PlatformBillEvent.Charge, FinancialEnum.BillStatus.Effective);

        }

    }

    /**
     * 退回押金
     * @param depositOrder
     */
    public boolean returnDepositOrder(DepositOrder depositOrder){

        if(OrderEnum.DepositReturnStatus.Yes.name().equals(depositOrder.getReturnStatus())){
            log.error("押金订单[{}]已提现！", depositOrder.getOrderNo());
            return true;
        }

        saveChargeData(depositOrder.getComId(), depositOrder.getChargeOrderId(), OrderAction.PB.RetuenDePositOrder, new HashMap(),
                "押金金额：" + (depositOrder.getReturnAmount() / 100.0) + "元");

        boolean result = false;

        if (PayEnum.PayWay.Wxpay.name().equals(depositOrder.getPayWay())) {
            /** todo 修改为宝付退款
            try {
                wxManager.getPayService(depositOrder.getComId());
                wxBasePayService.refundOrder(depositOrder.getOutTradeNo(), OrderUtil.getWxOrderNo(),
                        PayUtil.toTotalFee(depositOrder.getAmount()), PayUtil.toTotalFee(depositOrder.getReturnAmount()));

                saveChargeData(depositOrder.getComId(), depositOrder.getChargeOrderId(), OrderAction.PB.ReturnDepositOrderSuccess,
                        new HashMap(), "无");
                result =  true;
            } catch (Exception ex) {
                log.error(StrUtil.format("押金订单[{}]微信提现异常", depositOrder.getOrderNo()), ex);
                saveChargeData(depositOrder.getComId(), depositOrder.getChargeOrderId(), OrderAction.PB.RetuenDePositOrderFail,
                        new HashMap(), String.format("开始退回/提现押金失败：%s", ex.getMessage()));
            }*/
                try {
                    String type ="chargePb";
                    String refundNo = OrderUtil.getWxOrderNo();
                    baofuPayService.refundOrder(depositOrder.getOutTradeNo(), refundNo, PayUtil.toTotalFee(depositOrder.getAmount()),PayUtil.toTotalFee(depositOrder.getReturnAmount()),type,depositOrder.getComId());
                    BaofuRefund baofuRefund = new BaofuRefund();
                    baofuRefund.setComId(depositOrder.getComId());
                    baofuRefund.setRefundAmount(PayUtil.toTotalFee(depositOrder.getReturnAmount()));
                    baofuRefund.setOutRefundNo(refundNo);
                    baofuRefund.setRefundState("REFUND");
                    baofuRefund.setOutTradeNo(depositOrder.getOutTradeNo());
                    baofuRefund.setType(type);
                    baofuRefundService.addRefund(baofuRefund);
                    //todo 回调处理
//                    saveChargeData(depositOrder.getComId(), depositOrder.getChargeOrderId(), OrderAction.PB.ReturnDepositOrderSuccess,
//                            new HashMap(), "无");
//                    result =  true;
                }catch (ServiceException e){
                    log.error(StrUtil.format("押金订单[{}]微信提现异常", depositOrder.getOrderNo()), e);
                    saveChargeData(depositOrder.getComId(), depositOrder.getChargeOrderId(), OrderAction.PB.RetuenDePositOrderFail,
                            new HashMap(), String.format("开始退回/提现押金失败：%s", e.getMessage()));
                }
              } else if (PayEnum.PayWay.Alipay.name().equals(depositOrder.getPayWay())) {

            try {
                // TODO 支付宝退款不能用， 需要做根据comid切换到对应服务商
//                Map refundResult = payService.refund(outTradeNo, payUtil.toDoubleAmount(totalFee));

                saveChargeData(depositOrder.getComId(), depositOrder.getChargeOrderId(), OrderAction.PB.ReturnDepositOrderSuccess,
                        new HashMap<>(), "无");
                result = true;
            } catch (Exception e) {
                log.error(StrUtil.format("押金订单[{}]支付宝提现异常", depositOrder.getOrderNo()), e);
                saveChargeData(depositOrder.getComId(), depositOrder.getChargeOrderId(), OrderAction.PB.RetuenDePositOrderFail,
                        new HashMap(), String.format("开始退回/提现押金失败：%s", e.getMessage()));
            }
        }

        depositOrder.setReturnTime(DateUtil.date());
        depositOrder.setReturnStatus(result ? OrderEnum.DepositReturnStatus.Yes.name() : OrderEnum.DepositReturnStatus.No.name());
        depositOrderService.saveRow(depositOrder);

        if(result) {
            //插入用户账单流水：押金提现
            userBillService.addBill(depositOrder.getComId(), depositOrder.getMemberUserId(), depositOrder.getOutTradeNo(),
                    depositOrder.getOrderNo(), depositOrder.getReturnAmount(), FinancialEnum.IncomeOrExpend.Income,
                    FinancialEnum.UserBillEvent.DepositCashout, FinancialEnum.BillStatus.Effective);

            //插入平台账单流水：押金提现
            platformBillService.addBill(depositOrder.getComId(), depositOrder.getOutTradeNo(), depositOrder.getOrderNo(),
                    depositOrder.getReturnAmount(), FinancialEnum.IncomeOrExpend.Expend,
                    FinancialEnum.PlatformBillEvent.DepositCashout, FinancialEnum.BillStatus.Effective);

        }
        return result;

    }

    /**
     * 查询指定端口上的充电宝状态
     * 使用场景：扫码设备后，调用此方法查询所有端口状态（不一定用得到）
     * @param comId
     * @param pileId
     * @param orderId
     * @param port
     */
    public void queryPortStatus(String comId,String pileId,String orderId,Integer port) {
//        getAvailableDevice(order.getDeviceCode());

        try {
            Pile pile = pileService.getRow(pileId);
            String command = serviceFactory.getRpcPBDeviceService(pile.getProtocol())
                    .reqQueryPort(orderId, new DeviceKey(pile.getImei()), port);
            saveChargeData(comId, orderId, OrderAction.PB.QueryPortStatus,command,
                    "查询指定端口状态来确认出柜是否成功");
        }catch (Exception ex){
            log.error("查询指定端口上的充电宝状态出错:{}-{}", pileId, port);
        }
    }

    /**
     * 押金微信支付成功回调
     *
     * @param request
     */
    public void depositWxPayNotify(HttpServletRequest request, String comId) {
        WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = null;
        try {
            wxManager.getPayService(comId);
            wxPayOrderNotifyV3Result = wxBasePayService.parseOrderNotify(request);
        } catch (WxPayException e) {
            log.error("解析微信支付通知失败", e);
        }

        DepositOrder depositOrder = depositOrderService.getRow("out_trade_no", wxPayOrderNotifyV3Result.getResult().getOutTradeNo());
        if (null == depositOrder) {
            log.error("微信订单" + wxPayOrderNotifyV3Result.getResult().getOutTradeNo() + "不存在！");
            return;
        }
//        order.setTransactionId(wxPayOrderNotifyV3Result.getResult().getTransactionId());
        depositOrderService.updateRow(depositOrder);

        pbOrderManager.depositOrderPaid(depositOrder.getOrderNo(),
                DateUtil.parse(wxPayOrderNotifyV3Result.getResult().getSuccessTime()));
    }

    /**
     * 订单自动计时、计费
     * @param order
     */
    public void autoTiming(Order order){

        PbOrder pbOrder = pbOrderService.getRow("charge_order_id", order.getChargeOrderId());

        int rentDuration = getOrderRentSeconds(order);
        int paySeconds = pbOrderService.getCeilTime(rentDuration,pbOrder.getFreeDuration(), pbOrder.getDuration());
        int chargeAmount = pbOrderService.getChargeAmount(pbOrder, paySeconds);

        Order updateOrder = new Order();
        order.setChargeOrderId(pbOrder.getChargeOrderId());
        order.setDuration(rentDuration);
        order.setPaySeconds(paySeconds);//计算收费时间
        order.setChargeAmount(chargeAmount);//租用金额
        order.setOrderAmount(chargeAmount);
        order.setPayAmount(chargeAmount);//实际支付金额
        orderService.updateRow(order);

        if(chargeAmount < pbOrder.getMaxAmount()){
            saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.TimeAndCharge,
                    updateOrder, "每30分钟计费一次，金额：" + (chargeAmount / 100.0));
            return;
        }

        saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.OverAmountAutoStop,
                updateOrder, "达到封顶金额，自动结束");
        rentFinish(order.getOrderNo(),OrderStopReason.PB.OVERTIME.name(), null, null,false, true);
    }

    /***************************************** 与租借流程无关的方法 *****************************************/

    /**
     * 获取可用设备
     * @param code
     */
    public Pile getAvailableDevice(String code){
        Pile pile = pileService.getRow("code", code);
        if (pile == null) {
            throw new ServiceException(DeviceCode.NoDevice);
        }

        if (!DeviceEnum.OpenStatus.Enable.name().equals(pile.getOpenState())) {
            throw new ServiceException(DeviceCode.DeviceNotEnabled);
        }

        if (!DeviceEnum.DeviceStatus.Online.name().equals(pile.getDeviceStatus())) {
            throw new ServiceException(DeviceCode.DeviceOffline);
        }

        //添加判断是否有可借的充电宝
        List<Gun> gunList = gunService.getList("device_pile_id", pile.getDevicePileId());
        boolean noneMatch = gunList.stream().noneMatch(gun ->
                StrUtil.isNotEmpty(gun.getSn()) && null != gun.getSoc() && gun.getSoc() > 80);
        if(noneMatch){
            throw new ServiceException(DeviceCode.NoRentDevice);
        }

        return pile;
    }

    /**
     * 检查租借订单是否达到上限
     * @param userId
     */
    public void checkRentLimit(String comId, String userId){
        int maxRentSize = 0;
        RentSetting rentSetting = rentSettingService.getRow("com_id", comId);
        if(null != rentSetting){
            maxRentSize = rentSetting.getMaxOrders();
        }

        if(maxRentSize > 0) {
            List<Order> orderList = orderService.getList(new String[]{"member_user_id", "status", "device_type"},
                    new Object[]{userId, OrderEnum.Status.Charging.name(), DeviceTypeEnum.PB.getKey()});
            if (orderList.size() + 1 > maxRentSize) {
                throw new ServiceException(StrUtil.format("最多只允许同时租借{}个充电宝", maxRentSize));
            }
        }

    }

    /**
     * 获取用户免费充电状态
     * @param userId
     * @param stationId
     * @param pileId
     */
    public boolean getFreeStatus(String userId, String stationId, String pileId,String comId){
        boolean isFree = deviceManager.checkFreeUser(userId, stationId, pileId,comId);
        return isFree;
    }

    /**
     * 保存记录订单操作日志
     * @param orderId 订单ID
     * @param action 操作类型
     * @param data 其他数据
     * @param remark 描述
     */
    public void saveChargeData(String comId, String orderId, OrderAction.PB action, Object data, String remark){
        DataLog dataLog = new DataLog();
        dataLog.setComId(comId);
        dataLog.setChargeOrderId(orderId);
        dataLog.setAction(action.name());
        dataLog.setActionZh(action.getValue());
        dataLog.setCreateTime(DateUtil.date());
        dataLog.setJsonData(cn.hutool.json.JSONUtil.toJsonStr(data));
        dataLog.setRemark(remark);
        dataLogService.saveRow(dataLog);
    }

    /**
     * 获取收费描述
     * @param fee
     * @return
     */
    public String getItemPriceText(FeePbPrice fee){

        String durationPrice = "";
        if(fee.getDuration() < 3600){
            durationPrice = (fee.getDuration() / 60) + "分钟";
        }else{
            durationPrice = (fee.getDuration() / 3600) + "小时";
        }

        return String.format("%s分钟内归还免费，%s元/%s，%s元/24小时,%s元封顶,不足%s按%s收费",
                fee.getFreeDuration()/60,
                NumberUtil.div(fee.getPrice().intValue(), 100, 2),
                durationPrice,
                NumberUtil.div(fee.getDaysAmount().intValue(), 100, 2),
                NumberUtil.div(fee.getMaxAmount().intValue(), 100, 2),
                durationPrice,
                durationPrice);
    }

    /**
     * 获取订单租用时间（秒）
     *
     * @param order
     * @return
     */
    public int getOrderRentSeconds(Order order) {

        Date endTime = order.getEndTime();
        if(null == endTime){
            endTime = DateUtil.date();
        }

        int rentSeconds = (int)DateUtil.between(endTime, order.getStartTime(), DateUnit.SECOND);
        return rentSeconds;
    }


    /**
     * 获取租借中订单列表
     *
     * @return
     */
    public List<OrderVO> getRentingOrders() {
        String userId = userService.get().getMemberUserId();
        List list = service.getList("member_user_id", "status", userId, OrderEnum.Status.Charging.name());
        return BeanUtil.copyToList(list, OrderVO.class);
    }

    /**
     * 获取未完成的订单列表
     *
     * @return
     */
    public List<OrderItemVO> getUnpaidOrders() {
        String userId = userService.get().getMemberUserId();
        List<OrderItemVO> list = orderService.getViewModelList("get_pb_unpaid_orders", OrderItemVO.class, new String[]{userId});
        return list;
    }

    /***************************************** RpcPBOrderNotifyService实现类 *****************************************/

    /**
     * 设备上报：充电宝归还
     *
     * @param seq
     * @param imei
     * @param port
     * @return
     */
    @Override
    public void reqPortReturn(String cmd, String seq, String imei, String protocol,PBPort port) {

        //当comId为空时，属于厂商测试，不需要处理数据
        try {
            serviceFactory.getRpcPBDeviceService(protocol).respReturn(seq, new DeviceKey(imei), port.getPort(), 1);
        } catch (Exception ex) {
            log.error("回复充电宝归还失败-{}:{}", imei, ex);
        }


        Pile devicePile = pileService.getRow("imei",imei);
        if(devicePile == null)
            return;



        Gun gun = gunService.getRow(new String[]{"device_pile_id", "port"}, new Object[]{devicePile.getDevicePileId(), port.getPort()});
        if (gun != null) {
            gun.setSn(port.getSn());
            gun.setSoc(port.getSoc());
            gun.setStatus(String.valueOf(port.getState()));
            gunService.saveRow(gun);
        } else {
            log.warn("归还设备{}-{}端口不存在，忽略更新端口状态", imei, port.getPort());
        }

        if (devicePile != null) {
            Order order = pbOrderService.getViewModel("get_pb_order_by_sn", Order.class, new String[]{port.getSn(), OrderEnum.Status.Charging.name(),devicePile.getComId()});
            if(order != null) {
                saveChargeData(order.getComId(), order.getChargeOrderId(), OrderAction.PB.DeviceReturnReq,
                        cmd, String.format("设备归还成功，SN码为：%s", port.getSn()));
                pbOrderManager.rentFinish(order.getOrderNo(), OrderStopReason.PB.USER_RETURN.name(),devicePile.getDevicePileId(), port.getPort(), false, false);

            }else{
                log.error("设备归还找不到对应的订单，设备码：{}，归还SN：{}", imei, port.getSn());
            }
        }


    }

    /**
     * 设备响应：充电宝租借结果
     *
     * @param seq
     * @param imei
     * @param state 租用结果 0 为正常，1 为没有充电宝，2 为无可租用，4 超时
     * @param port
     */
    @Override
    public void respRent(String cmd, String seq, String imei, Integer state, PBPort port) {
        //当comId为空时，属于厂商测试，不需要处理数据
        Pile devicePile = pileService.getRow("imei",imei);
        if(StringUtil.isEmpty(devicePile.getComId()))
            return;

        Order order = orderService.getRow(seq);
        if(null == order){
            log.warn("seq[{}]订单不存在，忽略更新订单状态", seq);

        }
        saveChargeData(order.getComId(), seq, OrderAction.PB.StartRentResp, cmd, "state："+state);
        this.updateOrderStatus(seq, state, port.getSn(),port.getPort());
    }

    /**
     * 设备响应：查询指定端口状态
     *
     * @param seq
     * @param imei
     * @param port
     * @return
     */
    @Override
    public void respQueryPort(String cmd, String seq, String imei, PBPort port) {
        //当comId为空时，属于厂商测试，不需要处理数据
        Pile devicePile = pileService.getRow("imei",imei);
        if(StringUtil.isEmpty(devicePile.getComId()))
            return;

        confirmOrderStatus(seq,null == port ? null : port.getSn());
    }

    /**
     * 宝付押金微信支付成功回调
     *
     *
     */
    public void baofuDepositWxPayNotify(String outTradeNo, String successTime, String comId) {


        DepositOrder depositOrder = depositOrderService.getRow("out_trade_no",outTradeNo);
        if (null == depositOrder) {
            log.error("微信订单" + outTradeNo + "不存在！");
            return;
        }
//        order.setTransactionId(wxPayOrderNotifyV3Result.getResult().getTransactionId());
        depositOrderService.updateRow(depositOrder);

        pbOrderManager.depositOrderPaid(depositOrder.getOrderNo(),
                DateUtil.parse(successTime));
    }

}
